import { Map as MapboxMap, LngLatBounds } from 'mapbox-gl';

/**
 * 拉框缩放选项
 */
export interface BoxZoomOptions {
  /**
   * 拉框样式
   */
  boxStyle?: {
    /**
     * 边框颜色
     */
    borderColor?: string;

    /**
     * 边框宽度
     */
    borderWidth?: number;

    /**
     * 背景颜色
     */
    backgroundColor?: string;
  };
}

/**
 * 拉框缩放处理器
 *
 * 实现不需要按住Shift键的拉框缩放功能
 */
class BoxZoomHandler {
  /**
   * 是否已启用
   */
  private enabled = false;

  /**
   * 是否正在拖拽
   */
  private isDragging = false;

  /**
   * 起始点坐标
   */
  private startPoint: { x: number; y: number } | null = null;

  /**
   * 拉框DOM元素
   */
  private boxElement: HTMLDivElement | null = null;

  /**
   * 鼠标按下事件处理器
   */
  private mouseDownHandler: ((e: MouseEvent) => void) | null = null;

  /**
   * 鼠标移动事件处理器
   */
  private mouseMoveHandler: ((e: MouseEvent) => void) | null = null;

  /**
   * 鼠标抬起事件处理器
   */
  private mouseUpHandler: ((e: MouseEvent) => void) | null = null;

  /**
   * 选项
   */
  private options: BoxZoomOptions;

  /**
   * 创建拉框缩放处理器
   *
   * @param map Mapbox地图实例
   * @param options 拉框缩放选项
   */
  constructor(
    private map: MapboxMap,
    options: BoxZoomOptions = {},
  ) {
    this.options = options;
    this.createBoxElement();
    this.createEventHandlers();
  }

  /**
   * 获取是否已启用
   */
  isEnabled(): boolean {
    return this.enabled;
  }

  /**
   * 启用拉框缩放
   *
   * @param options 拉框缩放选项
   */
  enable(options?: BoxZoomOptions): void {
    if (this.enabled) return;

    if (options) {
      this.options = { ...this.options, ...options };
      this.updateBoxStyle();
    }

    // 禁用原生的BoxZoom处理器（按Shift拖拽）
    this.map.boxZoom.disable();

    // 添加事件监听
    if (this.mouseDownHandler) {
      const mapCanvas = this.map.getCanvas();
      mapCanvas.addEventListener('mousedown', this.mouseDownHandler);
    }

    this.enabled = true;
  }

  /**
   * 禁用拉框缩放
   */
  disable(): void {
    if (!this.enabled) return;

    // 启用原生的BoxZoom处理器
    this.map.boxZoom.enable();

    // 移除事件监听
    if (this.mouseDownHandler) {
      const mapCanvas = this.map.getCanvas();
      mapCanvas.removeEventListener('mousedown', this.mouseDownHandler);
    }

    // 移除拉框
    this.removeBox();

    // 重置状态
    this.enabled = false;
    this.isDragging = false;
    this.startPoint = null;
  }

  /**
   * 销毁处理器
   */
  destroy(): void {
    // 先禁用功能
    this.disable();

    // 移除拉框元素
    if (this.boxElement && this.boxElement.parentNode) {
      this.boxElement.parentNode.removeChild(this.boxElement);
      this.boxElement = null;
    }

    // 清除事件处理器
    this.mouseDownHandler = null;
    this.mouseMoveHandler = null;
    this.mouseUpHandler = null;
  }

  /**
   * 切换启用/禁用状态
   *
   * @param options 如果启用，可以提供选项
   * @returns 切换后的状态
   */
  toggle(options?: BoxZoomOptions): boolean {
    if (this.enabled) {
      this.disable();
      return false;
    } else {
      this.enable(options);
      return true;
    }
  }

  /**
   * 创建拉框元素
   */
  private createBoxElement(): void {
    const container = this.map.getContainer();

    this.boxElement = document.createElement('div');
    this.boxElement.className = 'mapboxgl-boxzoom-custom';

    this.updateBoxStyle();

    container.appendChild(this.boxElement);
  }

  /**
   * 更新拉框样式
   */
  private updateBoxStyle(): void {
    if (!this.boxElement) return;

    const { boxStyle } = this.options;

    Object.assign(this.boxElement.style, {
      position: 'absolute',
      top: '0',
      left: '0',
      width: '0',
      height: '0',
      display: 'none',
      border: `${boxStyle?.borderWidth || 2}px solid ${boxStyle?.borderColor || 'rgb(0, 120, 215)'}`,
      backgroundColor: boxStyle?.backgroundColor || 'rgba(0, 120, 215, 0.1)',
      pointerEvents: 'none',
      zIndex: '9999',
    });
  }

  /**
   * 创建事件处理器
   */
  private createEventHandlers(): void {
    this.mouseDownHandler = (e: MouseEvent) => {
      // 防止与其他地图交互冲突，仅处理左键点击
      if (e.button !== 0) return;

      // 开始拖拽
      this.isDragging = true;
      this.startPoint = { x: e.clientX, y: e.clientY };

      // 添加其他事件监听
      if (this.mouseMoveHandler) {
        document.addEventListener('mousemove', this.mouseMoveHandler);
      }

      if (this.mouseUpHandler) {
        document.addEventListener('mouseup', this.mouseUpHandler);
      }

      // 防止地图平移
      e.preventDefault();
      e.stopPropagation();
    };

    this.mouseMoveHandler = (e: MouseEvent) => {
      if (!this.isDragging || !this.startPoint || !this.boxElement) return;

      // 计算拉框位置和大小
      const currentPoint = { x: e.clientX, y: e.clientY };
      const bounds = this.calculateBoxBounds(this.startPoint, currentPoint);

      // 更新拉框样式
      Object.assign(this.boxElement.style, {
        display: 'block',
        left: `${bounds.left}px`,
        top: `${bounds.top}px`,
        width: `${bounds.width}px`,
        height: `${bounds.height}px`,
      });

      // 防止文本选择
      e.preventDefault();
    };

    this.mouseUpHandler = (e: MouseEvent) => {
      if (!this.isDragging || !this.startPoint) return;

      // 结束拖拽
      this.isDragging = false;

      // 计算拉框范围
      const endPoint = { x: e.clientX, y: e.clientY };

      // 只有当拉框面积足够大时才进行缩放
      const minDelta = 5;
      if (
        Math.abs(endPoint.x - this.startPoint.x) > minDelta &&
        Math.abs(endPoint.y - this.startPoint.y) > minDelta
      ) {
        // 将屏幕坐标转换为地理坐标
        const sw = this.map.unproject([
          Math.min(this.startPoint.x, endPoint.x),
          Math.max(this.startPoint.y, endPoint.y),
        ]);

        const ne = this.map.unproject([
          Math.max(this.startPoint.x, endPoint.x),
          Math.min(this.startPoint.y, endPoint.y),
        ]);

        // 执行缩放动画
        this.map.fitBounds(new LngLatBounds(sw, ne), {
          padding: 40,
          animate: true,
        });
      }

      // 移除拉框
      this.removeBox();

      // 移除临时事件监听
      document.removeEventListener('mousemove', this.mouseMoveHandler!);
      document.removeEventListener('mouseup', this.mouseUpHandler!);

      // 重置状态
      this.startPoint = null;

      // 防止默认事件和事件冒泡
      e.preventDefault();
      e.stopPropagation();
    };
  }

  /**
   * 计算拉框边界
   */
  private calculateBoxBounds(
    start: { x: number; y: number },
    end: { x: number; y: number },
  ) {
    return {
      left: Math.min(start.x, end.x),
      top: Math.min(start.y, end.y),
      width: Math.abs(end.x - start.x),
      height: Math.abs(end.y - start.y),
    };
  }

  /**
   * 移除拉框
   */
  private removeBox(): void {
    if (!this.boxElement) return;

    this.boxElement.style.display = 'none';
    this.boxElement.style.width = '0';
    this.boxElement.style.height = '0';
  }
}

/**
 * 缩放管理器, 用于管理地图的缩放功能 和 拉框缩放功能
 */
export class ZoomManager {
  private map: mapboxgl.Map;
  private mapLoaded = false;
  /**
   * 拉框缩放处理器 - 实现不用按Shift的拉框缩放功能
   * @private
   */
  private boxZoomHandler!: BoxZoomHandler;

  constructor(map: mapboxgl.Map) {
    this.map = map;
    map.on('load', () => {
      this.mapLoaded = true;
    });
    // 初始化拉框缩放处理器
    this.boxZoomHandler = new BoxZoomHandler(map, {
      boxStyle: {
        borderColor: '#3388ff',
        borderWidth: 2,
        backgroundColor: 'rgba(51, 136, 255, 0.15)',
      },
    });
  }

  getZoom() {
    return this.map.getZoom();
  }

  setZoom(zoom: number) {
    // 设置缩放级别
    return this.map.setZoom(zoom);
  }

  onZoomChange(callback: (zoom: number) => void): () => void {
    // 创建事件处理函数
    const handleZoomChange = () => {
      const currentZoom = this.getZoom();
      callback(currentZoom);
    };

    if (!this.mapLoaded) {
      // 如果地图尚未加载，等待加载完成后再绑定事件
      this.map.once('load', () => {
        this.map.on('zoom', handleZoomChange);
      });
      return () => {
        this.map.off('zoom', handleZoomChange);
      };
    }

    // 监听缩放事件
    this.map.on('zoom', handleZoomChange);

    // 返回取消监听的函数
    return () => {
      this.map.off('zoom', handleZoomChange);
    };
  }

  zoomIn() {
    this.map.zoomIn();
  }

  zoomOut() {
    this.map.zoomOut();
  }

  enableBoxZoom(options?: BoxZoomOptions): void {
    return this.boxZoomHandler.enable(options);
  }

  /**
   * 禁用拉框缩放功能
   */
  disableBoxZoom(): void {
    this.boxZoomHandler.disable();
  }

  /**
   * 切换拉框缩放的启用/禁用状态
   *
   * @param options 如果启用，可以提供选项
   * @returns 切换后的状态：true 表示已启用，false 表示已禁用
   */
  toggleBoxZoom(options?: BoxZoomOptions): boolean {
    return this.boxZoomHandler.toggle(options);
  }

  /**
   * 检查拉框缩放是否已启用
   *
   * @returns 拉框缩放是否已启用
   */
  isBoxZoomEnabled(): boolean {
    return this.boxZoomHandler.isEnabled();
  }
}
